/***************************************************
File:           LPK_TransformAnimator.cs
Authors:        Christopher Onorati
Last Updated:   6/10/2019
Last Version:   2018.3.14

Description:
  This component can be used to animate the transform of
  a GameObject.  In a way, this is like the Zero Engine's
  Action animation system.  This is designed, however, to
  be usable without touching a single line of code.  For a
  more intricate system that is designed with
  self-programming in mind, refer to Joshua Bigg's U-EAT
  system on the Unity Asset Store.

This script is a basic and generic implementation of its 
functionality. It is designed for educational purposes and 
aimed at helping beginners.

Copyright 2018-2019, DigiPen Institute of Technology
***************************************************/

using System.Runtime.CompilerServices;  /* Method Attributes */
using UnityEngine;
using UnityEditor;

namespace LPK
{

/**
* CLASS NAME  : LPK_TransformAnimator
* DESCRIPTION : Component used to animate the transform of a GameObject.
**/
public class LPK_TransformAnimator : LPK_Component
{
    /************************************************************************************/

    public enum LPK_TransformMode
    {
        WORLD,
        LOCAL,
    };

    public enum LPK_LoopingStyle
    {
        PlAY_ONCE,
        LOOP,
        PINGPONG,
    };

    /************************************************************************************/

    [Tooltip("How the change in the transform component will be applied.")]
    [Rename("Transform Mode")]
    public LPK_TransformMode m_eTransformMode;

    [Tooltip("How to loop the animaiton once finished.")]
    [Rename("Looping Style")]
    public LPK_LoopingStyle m_eLoopingStyle;

    [Tooltip("Game Object to change the enabled state of.  If not set assume self.")]
    [Rename("Modify Game Object")]
    public Transform m_pModifyGameObject;

    [Tooltip("Keyframes to use for this transform animation.")]
    public LPK_TransformAnimationKeyFrame[] m_KeyFrames;

    [Header("Event Sending Info")]

    [Tooltip("Event sent when the transform animator has finished a keyframe in the sequence.")]
    public LPK_EventSendingInfo m_KeyFrameFinishedEvent;

    [Tooltip("Event sent when the transform animator has finished its current sequence.")]
    public LPK_EventSendingInfo m_SequenceFinishedEvent;

    /************************************************************************************/

    //Stores initial properties of the previous keyframe for animation.
    LPK_TransformAnimationKeyFrame m_LastTransformValues = new LPK_TransformAnimationKeyFrame();

    //Stores the current properties of the desired keyframe for animation.
    LPK_TransformAnimationKeyFrame m_GoalTransformValues = new LPK_TransformAnimationKeyFrame();

    //Time passed from the previous animation frame to the current one.
    float m_flPassedTime = 0.0f;

    //Keeps track of the frame in the animation sequence.
    int m_iCounter = 0;

    //Counter modifier.  Only used in the PingPong looping style.
    int m_iCounterModifier = 1;

    /**
    * FUNCTION NAME: Start
    * DESCRIPTION  : Sets up what event to listen to for sprite and color modification.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    override protected void OnStart()
    {
        if (m_pModifyGameObject == null)
            m_pModifyGameObject = gameObject.transform;

        if(m_KeyFrames.Length == 0)
        {
            if (m_bPrintDebug)
                LPK_PrintWarning(this, "No keyframes set in animator.  BUG THIS.");
            return;
        }

        if (m_eTransformMode == LPK_TransformMode.WORLD)
        {
            m_LastTransformValues.m_vecTranslate = m_pModifyGameObject.position;
            m_LastTransformValues.m_vecRotate = m_pModifyGameObject.eulerAngles;
            m_LastTransformValues.m_vecScale = m_pModifyGameObject.localScale;
        }
        else
        {
            m_LastTransformValues.m_vecTranslate = m_pModifyGameObject.localPosition ;
            m_LastTransformValues.m_vecRotate = m_pModifyGameObject.localEulerAngles;
            m_LastTransformValues.m_vecScale = m_pModifyGameObject.localScale;
        }

        m_GoalTransformValues = m_KeyFrames[0];
    }

    /**
    * FUNCTION NAME: FixedUpdate
    * DESCRIPTION  : Manages animation looping state and animate calling.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void FixedUpdate()
    {
        bool bShouldAnimate = true;

        //Animation sequence finished.  Less than zero check for the PINGPONG state.
        if (m_iCounter >= m_KeyFrames.Length || m_iCounter < 0)
            bShouldAnimate = AnimationFinished();

        if (bShouldAnimate)
            Animate();
    }

    /**
    * FUNCTION NAME: Animate
    * DESCRIPTION  : Manages animation of the transform.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    [MethodImpl(256)]
    void Animate()
    {
        m_flPassedTime += Time.deltaTime;

        //Animation keyframe is done, set values.
        if (m_flPassedTime >= m_GoalTransformValues.m_flDuration)
        {
            if (m_eTransformMode == LPK_TransformMode.WORLD)
            {
                if(m_KeyFrames[m_iCounter].m_bModifyTranslation)
                    m_pModifyGameObject.position = m_GoalTransformValues.m_vecTranslate;
                if(m_KeyFrames[m_iCounter].m_bModifyRotation)
                    m_pModifyGameObject.eulerAngles = m_GoalTransformValues.m_vecRotate;
                if (m_KeyFrames[m_iCounter].m_bModifyScale)
                    m_pModifyGameObject.localScale = m_GoalTransformValues.m_vecScale;
            }
            else
            {
                if (m_KeyFrames[m_iCounter].m_bModifyTranslation)
                    m_pModifyGameObject.localPosition = m_GoalTransformValues.m_vecTranslate;
                if (m_KeyFrames[m_iCounter].m_bModifyRotation)
                    m_pModifyGameObject.localEulerAngles = m_GoalTransformValues.m_vecRotate;
                if (m_KeyFrames[m_iCounter].m_bModifyScale)
                    m_pModifyGameObject.localScale = m_GoalTransformValues.m_vecScale;
            }

            KeyFrameFinished();
            return;
        }

        //Play with set animation style.
        switch (m_GoalTransformValues.m_eAnimationStyle)
        {
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.LINEAR:
                LinearAnimate();
                break;
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.QUAD_IN:
                QuadInAnimate();
                break;
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.QUAD_OUT:
                QuadOutAnimate();
                break;
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.QUAD_IN_OUT:
                QuadInOutAnimate();
                break;
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.SIN_IN:
                SinInAnimate();
                break;
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.SIN_OUT:
                SinOutAnimate();
                break;
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.SIN_IN_OUT:
                SinInOutAnimate();
                break;
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.EASE_IN:
                EaseInAnimate();
                break;
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.EASE_OUT:
                EaseOutAnimate();
                break;
            case LPK_TransformAnimationKeyFrame.LPK_TransformAnimationStyle.EASE_IN_OUT:
                EaseInOutAnimate();
                break;
            default:
                LPK_PrintError(this, "Somehow a non-existant animation style was set.  Email c.onorati@digipen.edu if you did not modify this script.");
                break;
        }
    }

    /**
    * FUNCTION NAME: LinearAnimate
    * DESCRIPTION  : Animate the keyframe linearly.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void LinearAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        vecTranslateVal += (m_GoalTransformValues.m_vecTranslate - vecTranslateVal) * m_flPassedTime/m_GoalTransformValues.m_flDuration;
        vecRotateVal += (m_GoalTransformValues.m_vecRotate - vecRotateVal) * m_flPassedTime / m_GoalTransformValues.m_flDuration;
        vecScaleVal += (m_GoalTransformValues.m_vecScale - vecScaleVal) * m_flPassedTime / m_GoalTransformValues.m_flDuration;

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: QuadInAnimate
    * DESCRIPTION  : Animate the keyframe via QuadIn.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void QuadInAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        float curTime = m_flPassedTime / m_GoalTransformValues.m_flDuration;

        vecTranslateVal += (m_GoalTransformValues.m_vecTranslate - vecTranslateVal) * curTime * curTime;
        vecRotateVal += (m_GoalTransformValues.m_vecRotate - vecRotateVal) * curTime * curTime;
        vecScaleVal += (m_GoalTransformValues.m_vecScale - vecScaleVal) * curTime * curTime;

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: QuadOutAnimate
    * DESCRIPTION  : Animate the keyframe via QuadOut.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void QuadOutAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        float curTime = m_flPassedTime / m_GoalTransformValues.m_flDuration;

        vecTranslateVal += (m_GoalTransformValues.m_vecTranslate - vecTranslateVal) * -1 * curTime * (curTime - 2);
        vecRotateVal += (m_GoalTransformValues.m_vecRotate - vecRotateVal) * -1 * curTime * (curTime - 2);
        vecScaleVal += (m_GoalTransformValues.m_vecScale - vecScaleVal) * -1 * curTime * (curTime - 2);

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: QuadInOutAnimate
    * DESCRIPTION  : Animate the keyframe via QuadInOut.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void QuadInOutAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        Vector3 vecTranslateChange = m_GoalTransformValues.m_vecTranslate - m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateChange = m_GoalTransformValues.m_vecRotate - m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleChange = m_GoalTransformValues.m_vecScale - m_LastTransformValues.m_vecScale;

        float flCurTime = m_flPassedTime / (m_GoalTransformValues.m_flDuration / 2.0f);

        if (flCurTime < 1)
        {
            vecTranslateVal += (vecTranslateChange / 2) * flCurTime * flCurTime;
            vecRotateVal += (vecRotateChange / 2) * flCurTime * flCurTime;
            vecScaleVal += (vecScaleChange / 2) * flCurTime * flCurTime;
        }
        else
        {
            flCurTime -= 1;

            vecTranslateVal += (vecTranslateChange * -1) / 2 * (flCurTime * (flCurTime * (flCurTime - 2)) - 1);
            vecRotateVal += (vecRotateChange * -1) / 2 * (flCurTime * (flCurTime * (flCurTime - 2)) - 1);
            vecScaleVal += (vecScaleChange * -1) / 2 * (flCurTime * (flCurTime * (flCurTime - 2)) - 1);
        }

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: SinInAnimate
    * DESCRIPTION  : Animate the keyframe via SinIn.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void SinInAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        float flCosPassedTime = Mathf.Cos(m_flPassedTime / m_GoalTransformValues.m_flDuration * (Mathf.PI / 2));

        vecTranslateVal += ((m_GoalTransformValues.m_vecTranslate - vecTranslateVal) * -1 * flCosPassedTime) + m_GoalTransformValues.m_vecTranslate - vecTranslateVal;
        vecRotateVal += ((m_GoalTransformValues.m_vecRotate - vecRotateVal) * -1 * flCosPassedTime) + m_GoalTransformValues.m_vecRotate - vecRotateVal;
        vecScaleVal += ((m_GoalTransformValues.m_vecScale - vecScaleVal) * -1 * flCosPassedTime) + m_GoalTransformValues.m_vecScale - vecScaleVal;

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: SinOutAnimate
    * DESCRIPTION  : Animate the keyframe via SinOut.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void SinOutAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        float flSinPassedTime = Mathf.Sin(m_flPassedTime / m_GoalTransformValues.m_flDuration * (Mathf.PI / 2));

        vecTranslateVal += ((m_GoalTransformValues.m_vecTranslate - vecTranslateVal) * flSinPassedTime);
        vecRotateVal += ((m_GoalTransformValues.m_vecRotate - vecRotateVal) * flSinPassedTime);
        vecScaleVal += ((m_GoalTransformValues.m_vecScale - vecScaleVal) * flSinPassedTime);

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: SinInOutAnimate
    * DESCRIPTION  : Animate the keyframe via SinInOut.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void SinInOutAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        Vector3 vecTranslateChange = (m_GoalTransformValues.m_vecTranslate - m_LastTransformValues.m_vecTranslate) * -0.5f;
        Vector3 vecRotateChange = (m_GoalTransformValues.m_vecRotate - m_LastTransformValues.m_vecRotate) * -0.5f;
        Vector3 vecScaleChange = (m_GoalTransformValues.m_vecScale - m_LastTransformValues.m_vecScale) * -0.5f;

        float flSinPassedTime = Mathf.Cos((Mathf.PI * m_flPassedTime) / m_GoalTransformValues.m_flDuration ) - 1;

        vecTranslateVal += flSinPassedTime * vecTranslateChange;
        vecRotateVal += flSinPassedTime * vecRotateChange;
        vecScaleVal += flSinPassedTime * vecScaleChange;

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: EaseInAnimate
    * DESCRIPTION  : Animate the keyframe via exponential EaseIn.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void EaseInAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        Vector3 vecTranslateChange = m_GoalTransformValues.m_vecTranslate - m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateChange = m_GoalTransformValues.m_vecRotate - m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleChange = m_GoalTransformValues.m_vecScale - m_LastTransformValues.m_vecScale;

        vecTranslateVal += (vecTranslateChange * Mathf.Pow(2, 10 * (m_flPassedTime / m_GoalTransformValues.m_flDuration) - 1 ));
        vecRotateVal += (vecRotateChange * Mathf.Pow(2, 10 * (m_flPassedTime / m_GoalTransformValues.m_flDuration) - 1));
        vecScaleVal += (vecScaleChange * Mathf.Pow(2, 10 * (m_flPassedTime / m_GoalTransformValues.m_flDuration) - 1));

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: EaseOutAnimate
    * DESCRIPTION  : Animate the keyframe via exponential EaseOut.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void EaseOutAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        Vector3 vecTranslateChange = m_GoalTransformValues.m_vecTranslate - m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateChange = m_GoalTransformValues.m_vecRotate - m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleChange = m_GoalTransformValues.m_vecScale - m_LastTransformValues.m_vecScale;

        vecTranslateVal += (vecTranslateChange * -Mathf.Pow(2, 10 * (m_flPassedTime / m_GoalTransformValues.m_flDuration) + 1));
        vecRotateVal += (vecRotateChange * -Mathf.Pow(2, 10 * (m_flPassedTime / m_GoalTransformValues.m_flDuration) + 1));
        vecScaleVal += (vecScaleChange * -Mathf.Pow(2, 10 * (m_flPassedTime / m_GoalTransformValues.m_flDuration) + 1));

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: EaseInOutAnimate
    * DESCRIPTION  : Animate the keyframe via exponential EaseInOut.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void EaseInOutAnimate()
    {
        Vector3 vecTranslateVal = m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateVal = m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleVal = m_LastTransformValues.m_vecScale;

        Vector3 vecTranslateChange = m_GoalTransformValues.m_vecTranslate - m_LastTransformValues.m_vecTranslate;
        Vector3 vecRotateChange = m_GoalTransformValues.m_vecRotate - m_LastTransformValues.m_vecRotate;
        Vector3 vecScaleChange = m_GoalTransformValues.m_vecScale - m_LastTransformValues.m_vecScale;

        float flCurTime = m_flPassedTime / (m_GoalTransformValues.m_flDuration / 2.0f);

        if (flCurTime < 1)
        {
            vecTranslateVal += (vecTranslateChange / 2 * Mathf.Pow(2, 10 * (flCurTime - 1)));
            vecRotateVal += (vecRotateChange / 2 * Mathf.Pow(2, 10 * (flCurTime - 1)));
            vecScaleVal += (vecScaleChange / 2 * Mathf.Pow(2, 10 * (flCurTime - 1)));
        }
        else
        {
            flCurTime -= 1;

            vecTranslateVal += (vecTranslateChange / 2 * -Mathf.Pow(2, 10 * (flCurTime + 2)));
            vecRotateVal += (vecRotateChange / 2 * -Mathf.Pow(2, 10 * (flCurTime + 2)));
            vecScaleVal += (vecScaleChange / 2 * -Mathf.Pow(2, 10 * (flCurTime + 2)));
        }

        SetTransformValues(vecTranslateVal, vecRotateVal, vecScaleVal);
    }

    /**
    * FUNCTION NAME: SetTransformValues
    * DESCRIPTION  : Set the values of the transform after doing interperlation for the frame.
    * INPUTS       : _translate - Translate value to set.
    *                _rotate    - Rotation value to set.
    *                _scale     - Scale value to set.
    * OUTPUTS      : None
    **/
    void SetTransformValues(Vector3 _translate, Vector3 _rotate, Vector3 _scale)
    {
        if (m_eTransformMode == LPK_TransformMode.WORLD)
        {
            if(m_KeyFrames[m_iCounter].m_bModifyTranslation)
                m_pModifyGameObject.position = _translate;
            if (m_KeyFrames[m_iCounter].m_bModifyTranslation)
                m_pModifyGameObject.eulerAngles = _rotate;
            if(m_KeyFrames[m_iCounter].m_bModifyScale)
                m_pModifyGameObject.localScale = _scale;
        }
        else
        {
            if (m_KeyFrames[m_iCounter].m_bModifyTranslation)
                m_pModifyGameObject.transform.localPosition = _translate;
            if(m_KeyFrames[m_iCounter].m_bModifyRotation)
                m_pModifyGameObject.transform.localEulerAngles = _rotate;
            if (m_KeyFrames[m_iCounter].m_bModifyScale)
                m_pModifyGameObject.transform.localScale = _scale;
        }
    }

    /**
    * FUNCTION NAME: KeyFrameFinished
    * DESCRIPTION  : Updates what the last keyframe played in the animation was.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void KeyFrameFinished()
    {
        if (m_bPrintDebug)
            LPK_PrintDebug(this, "Finished keyframe.");

        DispatchKeyFrameFinsihedEvent();

        //Reset values.
        m_LastTransformValues = m_KeyFrames[m_iCounter];
        m_flPassedTime = 0.0f;
        m_iCounter += 1 * m_iCounterModifier;

        if (m_iCounter >= 0 && m_iCounter < m_KeyFrames.Length)
            m_GoalTransformValues = m_KeyFrames[m_iCounter];
    }

    /**
    * FUNCTION NAME: AnimationFinished
    * DESCRIPTION  : Manages how the animator responds if finished.
    * INPUTS       : None
    * OUTPUTS      : bool - True/False flag for whether or not the animator should animate again.
    **/
    bool AnimationFinished()
    {
        if (m_bPrintDebug)
            LPK_PrintDebug(this, "Finished animation sequence.");

        DispatchSequenceFinishedEvent();

        if (m_eLoopingStyle == LPK_LoopingStyle.LOOP)
        {
            if (m_KeyFrames.Length < 2)
            {
                if (m_bPrintDebug)
                    LPK_PrintWarning(this, "Cannot have LOOPING animation if there is only one frame.  Changing to PLAY_ONCE.");

                m_eLoopingStyle = LPK_LoopingStyle.PlAY_ONCE;
                return false;
            }

            m_iCounter = 0;
            m_GoalTransformValues = m_KeyFrames[m_iCounter];
            return true;
        }
        else if (m_eLoopingStyle == LPK_LoopingStyle.PINGPONG)
        {
            if(m_KeyFrames.Length < 2)
            {
                if (m_bPrintDebug)
                    LPK_PrintWarning(this, "Cannot have PINGPONG animation if there is only one frame.  Changing to PLAY_ONCE.");

                m_eLoopingStyle = LPK_LoopingStyle.PlAY_ONCE;
                return false;
            }

            //Going backwards.
            if (m_iCounterModifier == 1)
                m_iCounter -= 1;
            //Going forwards.
            else
                m_iCounter += 1;

            m_iCounterModifier *= -1;

            m_LastTransformValues = m_KeyFrames[m_iCounter];

            //Set the next goal.
            m_iCounter += 1 * m_iCounterModifier;
            m_GoalTransformValues = m_KeyFrames[m_iCounter];

            return true;
        }
        else
            return false;
    }

    /**
    * FUNCTION NAME: DispatchKeyFrameFinsihedEvent
    * DESCRIPTION  : Dispatches event when a keyframe in the animation sequence finishes.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void DispatchKeyFrameFinsihedEvent()
    {
        if(m_KeyFrameFinishedEvent != null && m_KeyFrameFinishedEvent.m_Event != null)
        {
            if(m_KeyFrameFinishedEvent.m_EventSendingMode == LPK_EventSendingInfo.LPK_EventSendingMode.ALL)
                m_KeyFrameFinishedEvent.m_Event.Dispatch(null);
            else if(m_KeyFrameFinishedEvent.m_EventSendingMode == LPK_EventSendingInfo.LPK_EventSendingMode.OWNER)
                m_KeyFrameFinishedEvent.m_Event.Dispatch(gameObject);

            if (m_bPrintDebug)
                LPK_PrintDebug(this, "Keyframe Finished event dispatched");
        }
    }

    /**
    * FUNCTION NAME: DispatchSequenceFinishedEvent
    * DESCRIPTION  : Dispatches event when the animation sequence finishes.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void DispatchSequenceFinishedEvent()
    {
        if(m_SequenceFinishedEvent != null && m_SequenceFinishedEvent.m_Event != null)
        {
            if(m_SequenceFinishedEvent.m_EventSendingMode == LPK_EventSendingInfo.LPK_EventSendingMode.ALL)
                m_SequenceFinishedEvent.m_Event.Dispatch(null);
            else if(m_SequenceFinishedEvent.m_EventSendingMode == LPK_EventSendingInfo.LPK_EventSendingMode.OWNER)
                m_SequenceFinishedEvent.m_Event.Dispatch(gameObject);

            if (m_bPrintDebug)
                LPK_PrintDebug(this, "Sequence Finished event dispatched");
        }
    }
}

/**
* CLASS NAME  : LPK_TransformAnimationKeyFrame
* DESCRIPTION : Transform properties to modify.
**/
[System.Serializable]
public class LPK_TransformAnimationKeyFrame
{
    /************************************************************************************/

    public enum LPK_TransformAnimationStyle
    {
        LINEAR,
        QUAD_IN,
        QUAD_OUT,
        QUAD_IN_OUT,
        SIN_IN,
        SIN_OUT,
        SIN_IN_OUT,
        EASE_IN,
        EASE_OUT,
        EASE_IN_OUT,
    };

    /************************************************************************************/

    [Tooltip("Set to make this keyframe animate translation.")]
    [Rename("Animate Translation")]
    public bool m_bModifyTranslation = false;

    [Tooltip("Values to use when modifying the translation.")]
    [Rename("Translation Values")]
    public Vector3 m_vecTranslate;

    [Tooltip("Set to make this keyframe animate rotation.")]
    [Rename("Animate Rotation")]
    public bool m_bModifyRotation = false;

    [Tooltip("Values to use when modifying the rotation.")]
    [Rename("Rotation Values")]
    public Vector3 m_vecRotate;

    [Tooltip("Set to make this keyframe animate scale.")]
    [Rename("Animate Scale")]
    public bool m_bModifyScale = false;

    [Tooltip("Values to use when modifying the scale.")]
    [Rename("Scale Values")]
    public Vector3 m_vecScale;

    [Tooltip("How to animate the transform for this keyframe.")]
    [Rename("Animation Style")]
    public LPK_TransformAnimationStyle m_eAnimationStyle;

    [Tooltip("Duration of time (in seconds) to get to interperlate to the values in this keyframe.  As in, it will take X seconds to reach the values selected for this keyframe.")]
    [Rename("Duration")]
    public float m_flDuration = 1.0f;
}

#if UNITY_EDITOR

[CustomEditor(typeof(LPK_TransformAnimator))]
public class LPK_TransformAnimatorEditor : Editor
{
    SerializedProperty transformMode;
    SerializedProperty loopingStyle;
    SerializedProperty modifyGameObject;
    SerializedProperty keyFrames;

    SerializedProperty eventTriggers;

    SerializedProperty keyFrameFinishedReceivers;
    SerializedProperty sequenceFinishedReceivers;

    /**
    * FUNCTION NAME: OnEnable
    * DESCRIPTION  : Save out serialized classes.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void OnEnable()
    {
        transformMode = serializedObject.FindProperty("m_eTransformMode");
        loopingStyle = serializedObject.FindProperty("m_eLoopingStyle");
        modifyGameObject = serializedObject.FindProperty("m_pModifyGameObject");
        keyFrames = serializedObject.FindProperty("m_KeyFrames");

        eventTriggers = serializedObject.FindProperty("m_EventTrigger");

        keyFrameFinishedReceivers = serializedObject.FindProperty("m_TransformAnimatorKeyFrameFinishedEvent");
        sequenceFinishedReceivers = serializedObject.FindProperty("m_TransformAnimatorSequenceFinishedEvent");
    }

    /**
    * FUNCTION NAME: OnInspectorGUI
    * DESCRIPTION  : Override GUI for inspector.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    public override void OnInspectorGUI()
    {
        LPK_TransformAnimator owner = (LPK_TransformAnimator)target;

        LPK_TransformAnimator editorOwner = owner.GetComponent<LPK_TransformAnimator>();

        EditorGUI.BeginDisabledGroup(true);
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel("Script");
        editorOwner = (LPK_TransformAnimator)EditorGUILayout.ObjectField(editorOwner, typeof(LPK_TransformAnimator), false);
        GUILayout.EndHorizontal();
        EditorGUI.EndDisabledGroup();

        //Undo saving.
        Undo.RecordObject(owner, "Property changes on LPK_TransformAnimator");

        GUILayout.Space(10);
        EditorGUILayout.LabelField("Base Properties", EditorStyles.boldLabel);

        owner.m_bPrintDebug = EditorGUILayout.Toggle(new GUIContent("Print Debug Info", "Toggle console debug messages."), owner.m_bPrintDebug);

        GUILayout.Space(10);
        EditorGUILayout.LabelField("Component Properties", EditorStyles.boldLabel);

        EditorGUILayout.PropertyField(transformMode, true);
        EditorGUILayout.PropertyField(loopingStyle, true);
        EditorGUILayout.PropertyField(modifyGameObject, true);
        EditorGUILayout.PropertyField(keyFrames, true);

        //Events
        EditorGUILayout.PropertyField(eventTriggers, true);
        EditorGUILayout.PropertyField(keyFrameFinishedReceivers, true);
        EditorGUILayout.PropertyField(sequenceFinishedReceivers, true);

        //Apply changes.
        serializedObject.ApplyModifiedProperties();
    }
}

[CustomPropertyDrawer(typeof(LPK_TransformAnimationKeyFrame))]
public class LPK_TransformAnimationKeyFrameDrawer : PropertyDrawer
{
    /**
    * FUNCTION NAME: OnGUI
    * DESCRIPTION  : Override GUI for inspector.
    * INPUTS       : position - Position of the property drawer.
    *                property - Property to draw.
    *                label    - Display info.
    * OUTPUTS      : None
    **/
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        SerializedProperty m_bModifyTranslation = property.FindPropertyRelative("m_bModifyTranslation");
        SerializedProperty m_vecTranslate = property.FindPropertyRelative("m_vecTranslate");
        SerializedProperty m_bModifyRotation = property.FindPropertyRelative("m_bModifyRotation");
        SerializedProperty m_vecRotate = property.FindPropertyRelative("m_vecRotate");
        SerializedProperty m_bModifyScale = property.FindPropertyRelative("m_bModifyScale");
        SerializedProperty m_vecScale = property.FindPropertyRelative("m_vecScale");
        SerializedProperty m_eAnimationStyle = property.FindPropertyRelative("m_eAnimationStyle");
        SerializedProperty m_flDuration = property.FindPropertyRelative("m_flDuration");

        int indent = EditorGUI.indentLevel;

        //NOTENOTE:  ++Due to being baked into an array of these in the LPK_TransformAnimator.
        EditorGUI.indentLevel++;

        m_bModifyTranslation.isExpanded = EditorGUI.Foldout(new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight),
                                                    m_bModifyTranslation.isExpanded, new GUIContent("Keyframe Properties"), true);

        if(m_bModifyTranslation.isExpanded)
        {
            EditorGUI.indentLevel++;
            
            int drawnProperties = 1;     

            //Transform
            EditorGUI.PropertyField(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * drawnProperties, position.width, EditorGUIUtility.singleLineHeight),m_bModifyTranslation);
            drawnProperties++;

            if (m_bModifyTranslation.boolValue)
            {
                EditorGUI.PropertyField(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * drawnProperties, position.width, EditorGUIUtility.singleLineHeight),m_vecTranslate);
                drawnProperties++;
            } 

            //Rotation
            EditorGUI.PropertyField(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * drawnProperties, position.width, EditorGUIUtility.singleLineHeight),m_bModifyRotation);
            drawnProperties++;

            if (m_bModifyRotation.boolValue)
            {
                EditorGUI.PropertyField(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * drawnProperties, position.width, EditorGUIUtility.singleLineHeight),m_vecRotate);
                drawnProperties++;
            } 

            //Scale
            EditorGUI.PropertyField(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * drawnProperties, position.width, EditorGUIUtility.singleLineHeight),m_bModifyScale);
            drawnProperties++;

            if (m_bModifyScale.boolValue)
            {
                EditorGUI.PropertyField(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * drawnProperties, position.width, EditorGUIUtility.singleLineHeight),m_vecScale);
                drawnProperties++;
            } 

            EditorGUI.PropertyField(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * drawnProperties, position.width, EditorGUIUtility.singleLineHeight),m_eAnimationStyle);
            drawnProperties++;

            EditorGUI.PropertyField(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight * drawnProperties, position.width, EditorGUIUtility.singleLineHeight),m_flDuration);
            drawnProperties++;
        }

        EditorGUI.indentLevel = indent;

        property.serializedObject.ApplyModifiedProperties();
    }

    /**
    * FUNCTION NAME: GetPropertyHeight
    * DESCRIPTION  : Set height for this property in the inspector.
    * INPUTS       : property - Property to draw.
    *                label    - Display info.
    * OUTPUTS      : None
    **/
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        SerializedProperty m_bModifyTranslation = property.FindPropertyRelative("m_bModifyTranslation");
        SerializedProperty m_bModifyRotation = property.FindPropertyRelative("m_bModifyRotation");
        SerializedProperty m_bModifyScale = property.FindPropertyRelative("m_bModifyScale");

        if(!m_bModifyTranslation.isExpanded)
            return  EditorGUIUtility.singleLineHeight;
        
        int shownPropertiesCount = 6;

        if (m_bModifyTranslation.boolValue)
            shownPropertiesCount++;

        if (m_bModifyRotation.boolValue)
            shownPropertiesCount++;

        if (m_bModifyScale.boolValue)
            shownPropertiesCount++;

        return  EditorGUIUtility.singleLineHeight * shownPropertiesCount;
    }
}

#endif  //UNITY_EDITOR

}   //LPK
    